// Author: Domien Nowicki, Bjorn Schobben


#include <assert.h>
#include "GLRendererWidgetImpl.h"
#include <QPainter>


GLRendererWidgetImpl::GLRendererWidgetImpl(QWidget *parent, const QGLWidget* shareWidget, Qt::WFlags flags): QGLWidget(parent, shareWidget, flags), textureHeight(1024), textureWidth(1024), textureData(0), lastTextureWidth(-1), lastTextureHeight(-1)
{
	// Initialize the screen texture
	SetTextureSize(width(), height());
}

GLRendererWidgetImpl::~GLRendererWidgetImpl()
{
	// Delete screen texture
	FreeTexture();
}

void GLRendererWidgetImpl::Update()
{
	// Move polygons from update list to current draw list, then clear the update list
	currentPolygons = updatePolygons;
	updatePolygons.clear();

	// Now update the screen
	updateGL();
}

void GLRendererWidgetImpl::initializeGL()
{
	glEnable(GL_BLEND);
	glDisable(GL_DEPTH_TEST);
	glBlendFunc(GL_SRC_ALPHA,GL_ONE);
	glHint(GL_PERSPECTIVE_CORRECTION_HINT, GL_NICEST);

	// Generate id for screen texture
	glGenTextures(1, &textureId);

	// Initialize screen texture with new size
	SetTextureSize(GetWidth(), GetHeight());
}

// Calculate next power of 2 for n
unsigned int GLRendererWidgetImpl::nextpow2(unsigned int n)
{
	unsigned int p = 2;

	while(p < n)
	{
		p *= 2;
	}

	return p;
}

void GLRendererWidgetImpl::FreeTexture()
{
	if (0 != textureData)
	{
		delete[] textureData;
		textureData = 0;
	}
}


void GLRendererWidgetImpl::SetTextureSize(unsigned int width, unsigned int height)
{
	FreeTexture();

	textureHeight = height;
	textureWidth = width;
	textureHeightPow2 = nextpow2(height);
	textureWidthPow2 = nextpow2(width);
	
	textureData = new unsigned char[textureHeightPow2 * textureWidthPow2 * 3];
}

// Convert 3D coordinate system to 2D screen coordinates
void GLRendererWidgetImpl::Start2D()
{
   int vPort[4];

   glGetIntegerv(GL_VIEWPORT, vPort);

   glMatrixMode(GL_PROJECTION);
   glPushMatrix();
   glLoadIdentity();

   glOrtho(vPort[0], vPort[0]+vPort[2], vPort[1]+vPort[3], vPort[1], -1, 1);
   glMatrixMode(GL_MODELVIEW);
   glPushMatrix();
   glLoadIdentity();
   glTranslatef(0.375, 0.375, 0.);

}


// Back to 3D coordinate system as before
void GLRendererWidgetImpl::End2D()
{
   glMatrixMode(GL_PROJECTION);
   glPopMatrix();   
   glMatrixMode(GL_MODELVIEW);
   glPopMatrix();       
}



void GLRendererWidgetImpl::SetTexture()
{
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
    glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);


	// Check if width or height of texture changed
	if (lastTextureWidth != textureWidth || lastTextureHeight != textureHeight)
	{
		// Width or height changed, so we need to allocate new video ram space for it

		glBindTexture(GL_TEXTURE_2D, textureId);
		// Delete old texture
		glDeleteTextures(1, &textureId);

		// Upload new one
		glBindTexture(GL_TEXTURE_2D, textureId);
		glTexImage2D(GL_TEXTURE_2D, 0, 3, textureWidthPow2, textureHeightPow2, 0, GL_RGB, GL_UNSIGNED_BYTE, textureData);
	}
	else
	{
		// Width and height are unchanged, so no need to allocate new space, just overwrite the old texture
		glBindTexture(GL_TEXTURE_2D, textureId);
		glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, textureWidthPow2, textureHeightPow2, GL_RGB, GL_UNSIGNED_BYTE, textureData);
	}

	lastTextureWidth = textureWidth;
	lastTextureHeight = textureHeight;
}

void GLRendererWidgetImpl::paintGL()
{
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

	// Switch into 2D coordinate system
	Start2D();

	glEnable(GL_TEXTURE_2D);
	SetTexture();

	glBegin(GL_QUADS);

	double texUnitX = (double)1.0 / (double)nextpow2(textureWidth);
	double texUnitY = (double)1.0 / (double)nextpow2(textureHeight);

	double thb = 0.0;
	double the = (double)(textureHeight) / textureHeightPow2;
	double twb = 0.0;
	double twe = (double)(textureWidth) / textureWidthPow2;

	double vhb = 0.0;
	double vhe = height();
	double vwb = 0.0;
	double vwe = width();

	glColor3ub(255, 255, 255);
	glTexCoord2f(twb, thb);
	glVertex2f(vwb, vhb);

	glTexCoord2f(twb, the);
	glVertex2f( vwb, vhe);

	glTexCoord2f(twe, the);
	glVertex2f( vwe, vhe);

	glTexCoord2f(twe, thb);
	glVertex2f(vwe, vhb);

	glEnd();
	glDisable(GL_TEXTURE_2D);


	std::list<Poly>::iterator it;


	// Draw each polygon
	for(it = currentPolygons.begin(); it != currentPolygons.end(); ++it)
	{
		const Poly& poly = *it;
		const Pixel& color = poly.GetColor();

		glColor4ub(color.GetRed(), color.GetGreen(), color.GetBlue(), color.GetAlpha());

		glBegin(GL_POLYGON);

		for(int i=0; i<poly.GetSize(); i++)
		{
			const Vector& point = poly.GetPoint(i);


			glVertex2i(point.GetIntX(), point.GetIntY());
		}

		glEnd();
	}


	// Switch back to previous coordinate system
	End2D();

}

void GLRendererWidgetImpl::resizeGL(int width, int height)
{
	// Change viewport
	glViewport(0, 0, width, height);

	// Change screen texture to new size
	SetTextureSize(width, height);
}

void GLRendererWidgetImpl::SetPixel(unsigned int x, unsigned int y, const Pixel& pixel)
{
	assert(0 != textureData);
	assert(x >= 0 && x < GetWidth());
	assert(y >= 0 && y < GetHeight());

	textureData[(y*textureWidthPow2+x)*3 + 0] = pixel.GetRed();
	textureData[(y*textureWidthPow2+x)*3 + 1] = pixel.GetGreen();
	textureData[(y*textureWidthPow2+x)*3 + 2] = pixel.GetBlue();

}

Pixel GLRendererWidgetImpl::GetPixel(unsigned int x, unsigned int y) const
{
	assert(0 != textureData);

	unsigned char red = textureData[(y*textureWidthPow2+x)*3 + 0];
	unsigned char green = textureData[(y*textureWidthPow2+x)*3 + 1];
	unsigned char blue = textureData[(y*textureWidthPow2+x)*3 + 2];

	return Pixel(red, green, blue);
}

unsigned int GLRendererWidgetImpl::GetHeight() const
{
	return textureHeight;
}

unsigned int GLRendererWidgetImpl::GetWidth() const
{
	return textureWidth;
}

void GLRendererWidgetImpl::DrawPolygon(const Poly& poly)
{
	updatePolygons.push_back(poly);
}



